Link to this headingOauth2

  • Used for Authorization
    • Enables third party application access to a limited version of the service
    • “Login with Facebook”

Why is OAuth still hard in 2023?

Grant types

  • Client Credentials: - Use if the Application is the Resource Owner
  • Authorization Code: - If the application is a web app that is executing on the same server. Usually returns a Session token
  • Resource Owner Password - If the Application can be trusted with the users credentials.
  • Implicit: Used for Single Page Applications
  • PKCE (Proof Key for Code Exchange): Used for native Application. Similar to Authorization Code
  • OpenID Connect:
  • Device Code:
  • Hybrid:

Roles:

  • Resource Owner: the entity that can grant access to a protected resource. Typically this is the end-user.
  • Application: an application requesting access to a protected resource on behalf of the Resource Owner.
  • Resource Server: the server hosting the protected resources. This is the API you want to access.
  • Authorization Server: the server that authenticates the Resource Owner, and issues Access Tokens after getting proper authorization. In this case, Auth0.
  • User Agent: the agent used by the Resource Owner to interact with the Application, for example a browser or a native application.

Link to this headingAuthorization Code

  • Get a token for your email app to access your google mail.

Protocol Flow:

sequenceDiagram Application (Client) ->> User (Resource Owner): 1. Authorization Request User (Resource Owner) ->> Application (Client): 2. Authorization Grant Application (Client) ->> Authorization Server: 3. Authorization Grant Authorization Server ->> Application (Client): 4. Access Token Application (Client) ->> Resource Server: 5. Access Token Resource Server ->> Application (Client): 6. Protected Resource

Link to this headingParameters

https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml

client_idauthorization request, token request
response_typeauthorization request
redirect_uriauthorization request, token request
scopeauthorization request, authorization response, token request, token response
stateauthorization request, authorization response
codeauthorization response, token request
errorauthorization response, token response
error_descriptionauthorization response, token response
error_uriauthorization response, token response
access_tokenauthorization response, token response
token_typeauthorization response, token response
expires_inauthorization response, token response
refresh_tokentoken request, token response
displayauthorization request
max_ageauthorization request
ui_localesauthorization request
claims_localesauthorization request
id_token_hintauthorization request
login_hintauthorization request
acr_valuesauthorization request
registrationauthorization request
requestauthorization request
request_uriauthorization request
id_tokenauthorization response, access token response
session_stateauthorization response, access token response
client_assertiontoken request
client_assertion_typetoken request
code_challengeauthorization request
code_challenge_methodauthorization request
claim_tokenclient request, token endpoint
pctclient request, token endpoint
pctauthorization server response, token endpoint
rptclient request, token endpoint
ticketclient request, token endpoint
upgradedauthorization server response, token endpoint
vtrauthorization request, token request
resourceauthorization request, token request
requested_token_typetoken request
subject_token_typetoken request
actor_token_typetoken request
issued_token_typetoken response
response_modeAuthorization Request
nfv_tokenAccess Token Response
issauthorization request, authorization response
ace_client_recipientidclient-rs request
ace_server_recipientidrs-client response
authorization_detailsauthorization request, token request, token response
dpop_jktauthorization request

Link to this headingAttacks

Link to this headingReusing Account tokens

Check that an old Code is expired after one use. Or after a short time period.

Link to this headingInsufficient redirect URI validation

Check the redirect URI for an attacker controlled site.
Check for a 302 Redirect on the scoped domain

Link to this headingCSRF

Check that State parameter

  • Make sure that it is checked by the server
  • Make sure that it is not a static value
  • Make sure that if you remove the parameter all together that the request fails.

Otherwise it can be subject to CSRF attacks

Link to this headingGoogle OAUTH Example

Setup:

  1. Go to https://console.cloud.google.com/apis/credentials and set up a new OAuth 2.0 Client IDs
  2. Create Credentials -> OAuth client ID -> Web Application
  3. Set Authorized JavaScript origins to http://test.generalzero.org:5000/
  4. Set Authorized redirect URIs to http://test.generalzero.org:5000/oauth/google/callback
  5. Set the client_id and client secret to env variables.

Flow:

  1. On visit to http://test.generalzero.org:5000/ there is a link to /oauth/google
  2. When the user visits /oauth/google the site returns a 302 redirect to Googles OAUTH endpoint with some parameters.
    • Google Parameters {"response_type": "code", "client_id":"{OAuth_Client_ID}", "redirect_uri": "http%3A%2F%test.generalzero.org%3A5000%2Foauth%2Fgoogle%2Fcallback", "scope": "email", "state":"fb139654c956ac64dfb39e0000000000"}
    • The site also sets a oauth_state cookie with the test.generalzero.org domain oauth_state=fb139654c956ac64dfb39e0000000000; HttpOnly; SameSite=Lax
  3. Google Does its flow and then makes a 302 redirect back to the redirect_uri.
    • Parameters: {"state":"fb139654c956ac64dfb39e0000000000", "code":"4%2F0AcvDMrDA-4AAAAAAAAAA8ffzMy4XJ0qSDFEt_1SfYAAAAxSl7YBDfFSdtALVsQL4JmYfdQ", "scope":"email+openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email", "authuser":"0", "prompt":"none"
  4. On the /oauth/google/callback the endpoint checks that the state paramater is the same as the set oauth_state cookie that is submitted on the request from the browser.
  5. This means that you have a valid token and code to query google as the server.
  6. THE SERVER makes a request to google to retrieve an access token.
    • Parameters: {"grant_type":"authorization_code", "code":"4%2F0AcvDMrDA-4AAAAAAAAAA8ffzMy4XJ0qSDFEt_1SfYAAAAxSl7YBDfFSdtALVsQL4JmYfdQ", "redirect_uri":"http%3A%2F%test.generalzero.org%3A5000%2Foauth%2Fgoogle%2Fcallback", "client_id":"{OAuth_Client_ID}", "client_secret":"{OAuth_Client_SECRET}"}
  7. Now the server has the access_token from google to request information about the user.

Test Code:

import express from "express"; import crypto from "crypto"; const PORT = 5000; const CLIENT_ID = process.env.CLIENT_ID as string; const CLIENT_SECRET = process.env.CLIENT_SECRET as string; const DOMAIN = "http://test.generalzero.org" const SECURE = false; const app = express(); app.use(express.static("static")); app.get("/oauth/google", (req, res) => { const params = new URLSearchParams(); params.set("response_type", "code"); params.set("client_id", CLIENT_ID); params.set("redirect_uri", `${DOMAIN}:${PORT}/oauth/google/callback`); params.set("scope", "email"); const state = crypto.randomBytes(16).toString("hex"); params.set("state", state); const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; if (SECURE){ res.set("Set-Cookie", `oauth_state=${state}; HttpOnly; Secure; SameSite=Lax`); } else { res.set("Set-Cookie", `oauth_state=${state}; HttpOnly; SameSite=Lax`); } res.redirect(url); }); app.get("/oauth/google/callback", async (req, res) => { const { code, state } = req.query; const oauthState = getCookie("oauth_state", req.headers.cookie as string); console.log("/oauth/google/callback") console.log(`req.headers.cookie: ${req.headers.cookie}`) console.log(`req.query: ${req.query}`) if (state !== oauthState) { return res.status(400).send("Invalid state"); } const accessToken = await exchangeCodeForToken(code as string); const userInfo = await getUserInfo(accessToken); res.header("Content-Type", "application/json").send(JSON.stringify(userInfo)); }); app.listen(PORT, () => { console.log(`Example app listening at ${DOMAIN}:${PORT}`); }); function getCookie(name: string, cookies: string) { console.log(`name:${name}, cookie: ${cookies} `) const cookie = cookies.split(";").find((cookie) => cookie.trim().startsWith(`${name}=`)); if (!cookie) { return null; } return cookie.split("=")[1]; } async function exchangeCodeForToken(code: string) { const resp = await fetch(`https://oauth2.googleapis.com/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: `${DOMAIN}:${PORT}/oauth/google/callback`, client_id: CLIENT_ID, client_secret: CLIENT_SECRET, }).toString(), }); if (!resp.ok) { throw new Error("Something went wrong"); } const { access_token } = (await resp.json()) as { access_token: string }; return access_token; } async function getUserInfo(accessToken: string) { const resp = await fetch(`https://www.googleapis.com/oauth2/v2/userinfo`, { headers: { Authorization: `Bearer ${accessToken}`, }, }); const json = (await resp.json()) as { email: string }; return json; }